package com.study.demo.aop.demo1_2.interceptor;

import com.study.demo.aop.demo1_2.LogOperationInvoker;
import com.study.demo.aop.demo1_2.operation.LogOperation;
import com.study.demo.aop.demo1_2.operation.LogOperationSource;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.*;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author: elvin
 * @time: 2019-10-16 13:48
 * @desc:
 **/
public abstract class LogAspectSupport   implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {

    @Nullable
    private LogOperationSource logOperationSource;

    @Nullable
    private BeanFactory beanFactory;

    private boolean initialized = false;

    private final SpelExpressionParser parser = new SpelExpressionParser();

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Nullable
    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.state(getLogOperationSource() != null, "The 'logOperationSources' property is required: " +
                "If there are no logable methods, then don't use a log aspect.");
    }

    @Override
    public void afterSingletonsInstantiated() {
        this.initialized = true;
    }

    @Nullable
    protected Object execute(LogOperationInvoker invoker, Object target, Method method, Object[] args) {
        // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
        if (this.initialized) {
            Class<?> targetClass = getTargetClass(target);
            //拿到当前被处理的方法和目标类, 通过 ElvinOperationSource 来判断是否需要执行代理方法
            LogOperationSource cacheOperationSource = getLogOperationSource();
            if (cacheOperationSource != null) {
                LogOperation operation = cacheOperationSource.getLogOperations(method, targetClass);
                if (operation != null) {
                    return execute(invoker,  target, method, args,targetClass, operation);
                }
            }
        }

        return invoker.invoke();
    }

    @Nullable
    private Object execute(final LogOperationInvoker invoker,Object target, Method method, Object[] args, Class<?> targetClass, LogOperation operation) {
        boolean passing = false;
        if(StringUtils.hasText(operation.getCondition())){
            ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
            EvaluationContext evaluationContext = new MethodBasedEvaluationContext(target, method, args, parameterNameDiscoverer);
            Method targetMethod = !Proxy.isProxyClass(targetClass) ?
                    AopUtils.getMostSpecificMethod(method, targetClass) : method;
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(targetMethod);
            if(parameterNames != null){
                for (int i = 0; i < parameterNames.length; i++) {
                    System.out.println(parameterNames[i]);
                    evaluationContext.setVariable(parameterNames[i], args[i]);
                }
            }
            Expression expression = parser.parseExpression(operation.getCondition());
            passing = Boolean.TRUE.equals(expression.getValue(evaluationContext, Boolean.class));
        }
        //具体处理过程
        Object returnValue = invokeOperation(invoker);

        if(passing){

            System.out.println("<--------------执行方法 : " +targetClass.getName() +"." + method.getName());
            for (Object arg : args) {
                System.out.println("方法参数: " + arg);
            }
            System.out.println("执行结果: " + returnValue + "------------------->");
        }

        return returnValue;
    }

    protected Object invokeOperation(LogOperationInvoker invoker) {
        return invoker.invoke();
    }


    protected LogOperationMetadata getLogOperationMetadata(LogOperation operation, Method method, Class<?> targetClass) {
        LogOperationMetadata metadata = new LogOperationMetadata(operation, method, targetClass);
        return metadata;
    }

    private Class<?> getTargetClass(Object target) {
        return AopProxyUtils.ultimateTargetClass(target);
    }

    @Nullable
    public LogOperationSource getLogOperationSource() {
        return logOperationSource;
    }

    public void setLogOperationSource(@Nullable LogOperationSource logOperationSource) {
        this.logOperationSource = logOperationSource;
    }

    protected static class LogOperationMetadata {

        private final LogOperation operation;

        private final Method method;

        private final Class<?> targetClass;

        private final Method targetMethod;

        private final AnnotatedElementKey methodKey;


        public LogOperationMetadata(LogOperation operation, Method method, Class<?> targetClass) {

            this.operation = operation;
            this.method = BridgeMethodResolver.findBridgedMethod(method);
            this.targetClass = targetClass;
            this.targetMethod = (!Proxy.isProxyClass(targetClass) ?
                    AopUtils.getMostSpecificMethod(method, targetClass) : this.method);
            this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);
        }
    }

}

